from typing import Optional, Tuple, Union
from fastapi import FastAPI, status, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.orm import Session
from datetime import datetime, timedelta
from models import get_db, UserKey, UsedLog, sessionmaker
from schema.schemas import (
    UsedLogCreate, UserKeyUpdate, UsedLogUpdate, 
    RealVideo2TextRequest, Video2TextRequest, Share2RealUrlRequest, ImitateTextRequest,
    Video2TextResponse, Share2RealUrlResponse, ImitateTextResponse, ErrorResponse,
    RemoveWatermarkRequest, RemoveWatermarkResponse
)
import requests
from urllib import request
import os
import json
from uuid import uuid1
from moviepy.editor import VideoFileClip
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from common.constants import KEYUSED_MAXTIMES
from common.douyin_parser import parse_douyin_url
import dashscope
from dashscope import Generation
import struct
from prompt.prompts import ALL_PROMPTS
import config  #导入以初始化日志配置
import logging
from functools import wraps
from common.watermark_remover import get_watermark_remover, cleanup_watermark_remover

logger = logging.getLogger("server")

# ===========================业务逻辑=====================================
# 装饰器 for 校验key函数
def verify_key_decorator(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        # 校验逻辑 ==============================
        # TODO: 实现具体的装饰器逻辑
        # =======================================
        return f(*args, **kwargs)
    return wrapper

# 校验key
def verify_key(key_str: str, req: Request) -> Tuple[bool, Optional[str]]:
    try:
        # 查询数据库进行校验 ======================
        sess = next(get_db())
        user_key = UserKey().read(sess, key_str)
        if user_key is None:
            return False, '无效key'
        # 获取今日0点的时间
        today_zero = datetime.combine(datetime.today(), datetime.min.time())
        # 查询该 key 对应的使用日志
        used_log = UsedLog().read(sess, key_str)
        if used_log and used_log.lastime >= today_zero and user_key.remainder <= 0:
            return False, 'out'  #用out指代：该key今日免费次数已用完，以方便前端处理
        if used_log!=None and used_log.lastime<today_zero:
            # 如果使用日志的最后使用时间早于今日0点，重置剩余次数为最大次数
            user_key.update(sess, key_str, KEYUSED_MAXTIMES)
        now = datetime.now()
        if not used_log:
            # 如果没有使用日志，创建一条
            UsedLog().create(sess, key_str, now, req.client.host, req.headers.get('user-agent'))
        else:
            used_log.update(sess, key_str, now, req.client.host, req.headers.get('user-agent'))
        # 每查询校验一次，剩余次数减一
        user_key.update(sess, key_str, user_key.remainder - 1)
        return True, None
    except Exception as e:
        logger.error(f'查询失败! error: {e}')
        return False, '服务器内部错误'

# 下载视频文件
def download_file(url: str) -> Tuple[bool, Optional[str]]:
    try:
        save_path = None
        # 发送HTTP请求获取文件内容
        response = requests.get(url, stream=True)
        response.raise_for_status()  # 检查请求是否成功

        # 打开文件以写入二进制模式
        file_name = str(uuid1())
        save_path = os.path.join(os.getcwd(), f'result/{file_name}.mp4')
        with open(save_path, 'wb') as file:
            # 分块写入文件，避免一次性加载大文件到内存
            for chunk in response.iter_content(chunk_size=8192):
                if chunk:
                    file.write(chunk)
        logger.info(f"文件下载成功，保存路径: {save_path}")
        return True, save_path
    except requests.RequestException as e:
        logger.error(f"文件下载失败: {e}")
        return False, None

# 从视频文件中提取音频并存储
def extract_audio_from_video(video_path: str) -> Tuple[bool, Optional[str]]:
    try:
        video = VideoFileClip(video_path)
        file_name = str(uuid1())
        audio_path = os.path.join(os.getcwd(), f'result/{file_name}.mp3')
        video.audio.write_audiofile(audio_path)  # 保存为MP3格式
        logger.info(f"音频提取成功，保存路径: {audio_path}")
        return True, audio_path
    except Exception as e:
        logger.error(f"提取音频失败: {e}")
        return False, None


# 记录使用日志
def record_used_log(key: str, ip: str, useragent: str):
    try:
        sess = next(get_db())
        used_log = sess.query(UsedLog).filter_by(key=key).first()
        if used_log is None:
            used_log = UsedLog(key=key, ip=ip, useragent=useragent)
            sess.add(used_log)
        used_log = UsedLog(key=key, lastime=datetime(), ip=ip, useragent=useragent)
        sess.query(UsedLog).update(used_log.dict(exclude_unset=True))
        sess.commit()  # 提交更改到数据库
        sess.refresh(used_log)  # 刷新对象状态，确保数据库中的数据与对象一致
    except Exception as e:
        logger.error(f'记录使用日志失败! {e}')
        raise e
        # 每查询校验一次，剩余次数减一

# 调用新野api从抖音分享链接获取真实视频url
def get_realurl_from_api(url: str) -> str | None:
    try:
        # 调用xs api从抖音分享链接获取真实视频url
        api = 'https://api.xinyew.cn/api/douyinjx?url='
        rep = requests.get(api + url)
        rep.raise_for_status()  # 检查请求是否成功
        
        # 解析响应数据
        json_rep = json.loads(rep.text)
        rep_code = json_rep['code']
        if rep_code != status.HTTP_200_OK:
            raise HTTPException(status_code=rep_code, detail=json_rep['msg'])
        return json_rep['data']['video_url']
    except Exception as e:
        logger.error(f'调用xs api失败! {e}')
        return None

# 调用douyin_parser.py中的函数从抖音分享链接获取真实视频url
def get_real_url(sharetext: str) -> str | None:
    # 调用douyin_parser.py中的函数从抖音分享链接获取真实视频url
    ret = parse_douyin_url(share_text=sharetext)
    if not ret[0]:
        return None
    return ret[1]

# 调用百炼api传递视频url提取文本
def extract_text_from_video(url: str) -> Tuple[bool, Optional[str]]:
    result_text = None
    try:
        # 调用百炼api传递视频url提取文本
        task_response = dashscope.audio.asr.Transcription.async_call(
            model='paraformer-mtl-v1',
            file_urls=[url],
            language_hints=['zh', 'en'])

        transcription_response = dashscope.audio.asr.Transcription.wait(
            task=task_response.output.task_id)

        if transcription_response.status_code == status.HTTP_200_OK:
            logger.info('提取文本结束!')
            output = transcription_response.output
            if output['task_status'] != 'SUCCEEDED':
                raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=output.message)
            transcription = output['results'][0]
            url = transcription['transcription_url']
            logger.info(f'文本获取url: {url}')
            result = json.loads(request.urlopen(url).read().decode('utf8'))
            #整理数据
            sentences = []
            for chunk in result['transcripts']:
                for sentence in chunk['sentences']:
                    sentences.append(sentence['text'])
            result_text = '\n'.join(sentences)
        else:
            raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=transcription_response.output.message)
    except Exception as e:
        logger.error(f'调用百炼api失败! {e}')
        return False, None
    return True, result_text  # 这里需要替换为实际的返回值和错误处理逻辑

# 获取视频时长
def get_mp4_duration(url: str) -> float | None:
    try:
        with requests.get(url, stream=True) as r:
            for chunk in r.iter_content(chunk_size=512):
                if b'mvhd' in chunk:
                    index = chunk.find(b'mvhd') + 4
                    time_scale = struct.unpack('>I', chunk[index+13:index+17])[0]
                    duration = struct.unpack('>I', chunk[index+17:index+21])[0]
                    return duration / time_scale
        return None
    except Exception as e:
        logger.error(f'获取视频时长失败! {e}')
        return None


# ===========================路由处理=====================================
app = FastAPI()

# 添加CORS中间件配置
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # 允许所有源，生产环境建议指定具体域名
    allow_credentials=True,
    allow_methods=["*"],  # 允许所有HTTP方法
    allow_headers=["*"],  # 允许所有请求头
)

app.mount("/static", StaticFiles(directory="static"), name="static")
app.mount("/result", StaticFiles(directory="result"), name="result")

# 修改根路由返回index.html
@app.get("/")
async def root():
    # 检查index.html文件是否存在
    index_path = os.path.join(os.getcwd(), "static/index.html")
    if not os.path.exists(index_path):
        return {"message": "请搜索添加微信号lvwker获取免费key，备注”免费key“。通过浏览器访问/docs查看接口文档。"}
    return FileResponse(index_path)

# 添加favicon.ico路由
@app.get("/favicon.ico", include_in_schema=False)
async def favicon():
    return FileResponse(os.path.join(os.getcwd(), "static/favicon.ico"))

# 供用户查询剩余次数
@app.get("/getremainder")
async def getremainder(key: str):
    """
    查询key的剩余可使用次数\n
    :param key: 密钥\n
    :return: 剩余次数
    """
    # 查询数据库进行校验 ======================
    sess = next(get_db())
    user_key = sess.query(UserKey).filter_by(key=key).first()
    if user_key is None:
        return HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid key")

    return {"remainder": user_key.remainder}  

## 供用户从本服务器访问结果数据文件
#@app.get("/result/{file_name}")
#async def result(file_name: str):
#    # 构建文件的完整路径
#    file_path = os.path.join(os.getcwd(), f"result/{file_name}")
#    # 检查文件是否存在
#    if not os.path.isfile(file_path):
#        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="文件未找到")
#    # 返回文件响应供用户下载
#    return FileResponse(file_path, filename=file_name)


## 从视频提取音频
#@app.post("/getaudio")
#async def getaudio(key: str, url: str, req: Request):
#    # 校验key
#    if not verify_key(key):
#        return HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid key")
#    # 下载视频文件
#    res, video_path = download_file(url)
#    if not res:
#        return HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Invalid url")
#    # 从视频文件中提取音频并存储
#    res, audio_path = extract_audio_from_video(video_path)
#    if not res:
#        return HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Extract audio failed")
#    
#    # 整理返回给客户端的结果文件路径
#    base_url = str(req.base_url)
#    audio_path = audio_path.replace(os.getcwd(), base_url.rstrip('/'))
#    return {"audio": audio_path}, status.HTTP_200_OK

# 从视频真实下载地址提取文本
@app.post("/realvideo2text")
async def realvideo2text(request_data: RealVideo2TextRequest, req: Request):
    """
    从视频真实下载地址提取文本\n
    :param request_data: 请求数据，包含密钥和视频真实下载地址\n
    :return: 提取的文本，内含有换行符
    """
    # 校验key
    ret, v = verify_key(request_data.key, req)
    if not ret:
        logger.info(f"[realvideo2text]Invalid key {request_data.key}")
        if v == 'out':
            return HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="out")
        else:
            return HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid key")
    # 解析文本
    res, text = extract_text_from_video(request_data.url)
    if not res:
        return HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="提取文案失败")
    return {"text": text}, status.HTTP_200_OK

# 从视频提取文本
@app.post(
    "/video2text",
    response_model=Video2TextResponse,
    responses={
        200: {
            "description": "成功提取视频文本",
            "model": Video2TextResponse,
            "content": {
                "application/json": {
                    "example": {"text": "这是从视频中提取的文本内容\n包含换行符的完整文案"}
                }
            }
        },
        400: {
            "description": "请求参数错误或视频时长超过限制",
            "model": ErrorResponse,
            "content": {
                "application/json": {
                    "example": {"detail": "视频时长超过系统负载上限"}
                }
            }
        },
        401: {
            "description": "密钥无效或今日免费次数已用完",
            "model": ErrorResponse,
            "content": {
                "application/json": {
                    "examples": {
                        "invalid_key": {"value": {"detail": "Invalid key"}},
                        "quota_exceeded": {"value": {"detail": "out"}}
                    }
                }
            }
        },
        500: {
            "description": "服务器内部错误，文本提取失败",
            "model": ErrorResponse,
            "content": {
                "application/json": {
                    "example": {"detail": "提取文案失败"}
                }
            }
        }
    },
    summary="从抖音视频提取文本",
    description="""
    从抖音视频分享链接中提取语音转文字内容。
    
    **功能说明：**
    - 支持抖音短视频分享链接解析
    - 自动获取视频真实下载地址
    - 将视频中的语音转换为文字
    - 视频时长限制：不超过612秒
    
    **使用限制：**
    - 需要有效的API密钥
    - 每个密钥每日有免费使用次数限制
    - 视频时长不能超过10分钟
    
    **返回格式：**
    - 提取的文本内容包含换行符
    - 按语音识别的句子结构组织
    """
)
async def video2text(request_data: Video2TextRequest, req: Request):
    """
    从视频提取文本\n
    :param request_data: 请求数据，包含密钥和抖音视频分享url\n
    :return: 提取的文本，内含有换行符
    """
    # 校验key
    if not verify_key(request_data.key, req):
        logger.info(f"[video2text]Invalid key {request_data.key}")
        return HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid key")
    # 获取真实视频url
    video_url = get_real_url(request_data.url)
    # 校验视频时长
    duration = get_mp4_duration(video_url)
    if duration is None or duration > 612:
        return HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="视频时长超过系统负载上限")
    # 解析文本
    res, text = extract_text_from_video(video_url)
    if not res:
        logger.error(f"[video2text]FAILED: {text}")
        return HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="提取文案失败")
    return {"text": text}

@app.post(
    "/share2realurl",
    response_model=Share2RealUrlResponse,
    responses={
        200: {
            "description": "成功解析分享链接",
            "model": Share2RealUrlResponse,
            "content": {
                "application/json": {
                    "example": {"text": "https://v.douyin.com/example/video.mp4"}
                }
            }
        },
        401: {
            "description": "密钥无效",
            "model": ErrorResponse,
            "content": {
                "application/json": {
                    "example": {"detail": "Invalid key"}
                }
            }
        },
        500: {
            "description": "解析分享链接失败",
            "model": ErrorResponse,
            "content": {
                "application/json": {
                    "example": {"detail": "解析分享链接失败"}
                }
            }
        }
    },
    summary="解析抖音分享链接获取真实视频URL",
    description="""
    将抖音视频分享链接解析为可直接下载的真实视频URL。
    
    **功能说明：**
    - 支持抖音短视频分享链接
    - 自动解析重定向链接
    - 返回视频的直接下载地址
    
    **支持的链接格式：**
    - 抖音APP分享的短链接
    - 包含跳转的分享文本
    
    **使用限制：**
    - 需要有效的API密钥
    - 每次调用会消耗使用次数
    """
)
async def share2realurl(request_data: Share2RealUrlRequest, req: Request):
    """
    从抖音视频分享链接获取真实视频url\n
    :param request_data: 请求数据，包含密钥和抖音的视频分享链接\n
    :return: 视频的真实url
    """
    # 校验key
    if not verify_key(request_data.key, req):
        logger.info(f"[share2realurl]Invalid key {request_data.key}")
        return HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid key")
    # 解析文本
    res, text = parse_douyin_url(request_data.share_text)
    if not res:
        logger.error(f"[share2realurl]FAILED: {text}")
        return HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="解析分享链接失败")
    return {"text": text}

@app.post(
    "/imitatetext",
    response_model=ImitateTextResponse,
    responses={
        200: {
            "description": "成功生成仿写文案",
            "model": ImitateTextResponse,
            "content": {
                "application/json": {
                    "example": {"text": "这是根据原文案风格生成的仿写内容，保持了相似的表达方式和语调。"}
                }
            }
        },
        401: {
            "description": "密钥无效",
            "model": ErrorResponse,
            "content": {
                "application/json": {
                    "example": {"detail": "Invalid key"}
                }
            }
        },
        500: {
            "description": "仿写文案失败",
            "model": ErrorResponse,
            "content": {
                "application/json": {
                    "example": {"detail": "仿写文案失败"}
                }
            }
        }
    },
    summary="AI文案仿写",
    description="""
    基于提供的原文案，使用AI生成风格相似的仿写内容。
    
    **功能说明：**
    - 使用通义千问AI模型进行文案仿写
    - 保持原文案的风格和语调
    - 生成新颖且相关的内容
    
    **适用场景：**
    - 营销文案创作
    - 内容创意扩展
    - 文案风格学习
    
    **使用限制：**
    - 需要有效的API密钥
    - 依赖阿里云百炼AI服务
    - 每次调用会消耗使用次数
    """
)
async def imitatetext(request_data: ImitateTextRequest, req: Request):
    """
    根据提供的文案进行仿写\n
    :param request_data: 请求数据，包含密钥和文案\n
    :return: 生成的文案
    """
    # 校验key
    if not verify_key(request_data.key, req):
        logger.info(f"[imitatetext]Invalid key {request_data.key}")
        return HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid key")
    # 调用百炼api
    messages = [
        {'role': 'system', 'content': ALL_PROMPTS['imitate_text']},
        {'role': 'user', 'content': request_data.text}
    ]
    # 百炼key通过环境变量设置
    res = Generation.call(
        model='qwen-turbo',
        messages=messages,
        result_format='message',
    )

    if res.status_code != status.HTTP_200_OK:
        logger.error('仿写文案失败!')
        return HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="仿写文案失败")
    return {"text": res.output.choices[0].message.content}

@app.post("/ping")
async def ping(req: Request):
    """
    简单的ping接口，用于测试服务状态\n
    :param req: 请求对象\n
    :return: 成功响应
    """
    try:
        # 获取所有请求参数
        query_params = dict(req.query_params)
        headers = dict(req.headers)
        client_info = {
            "host": req.client.host if req.client else None,
            "port": req.client.port if req.client else None
        }
        
        # 尝试获取请求体
        body = None
        try:
            body = await req.body()
            if body:
                body = body.decode('utf-8')
        except Exception:
            body = "无法解析请求体"
        
        # 打印所有客户端传递的参数
        logger.info(f"[ping] 收到ping请求:")
        logger.info(f"[ping] 查询参数: {query_params}")
        logger.info(f"[ping] 请求头: {headers}")
        logger.info(f"[ping] 客户端信息: {client_info}")
        logger.info(f"[ping] 请求体: {body}")
        
        return {"status": "pong", "message": "服务正常运行"}
    except Exception as e:
        logger.error(f"[ping] 处理ping请求失败: {e}")
        return {"status": "error", "message": "处理请求时发生错误"}

@app.get("/health")
async def health_check():
    """
    健康检查端点，用于检查服务状态
    :return: 健康状态响应
    """
    try:
        # 检查数据库连接
        db = next(get_db())
        db.execute("SELECT 1")
        db.close()
        
        return {
            "status": "healthy", 
            "message": "服务运行正常",
            "timestamp": datetime.now().isoformat()
        }
    except Exception as e:
        logger.error(f"[health] 健康检查失败: {e}")
        return {
            "status": "unhealthy", 
            "message": f"服务异常: {str(e)}",
            "timestamp": datetime.now().isoformat()
        }

@app.post(
    "/remove-watermark",
    response_model=RemoveWatermarkResponse,
    responses={
        200: {
            "description": "成功去除视频水印",
            "model": RemoveWatermarkResponse,
            "content": {
                "application/json": {
                    "example": {
                        "success": True,
                        "clean_video_url": "https://example.com/clean_video.mp4",
                        "original_video_info": {"title": "原视频标题", "description": "原视频描述"},
                        "processing_time": 2.5,
                        "used_fallback": False
                    }
                }
            }
        },
        401: {
            "description": "密钥无效",
            "model": ErrorResponse,
            "content": {
                "application/json": {
                    "example": {"detail": "Invalid key"}
                }
            }
        },
        500: {
            "description": "去水印处理失败",
            "model": ErrorResponse,
            "content": {
                "application/json": {
                    "example": {"detail": "去水印处理失败"}
                }
            }
        }
    },
    summary="视频去水印",
    description="""
    对视频进行去水印处理，优先使用第三方接口，失败时自动降级到本地逻辑。
    
    **功能说明：**
    - 支持抖音视频分享链接和URL
    - 优先调用第三方API进行去水印处理
    - 第三方API失败时自动降级到本地处理逻辑
    - 返回去水印后的视频URL和相关信息
    
    **处理流程：**
    1. 验证用户密钥
    2. 优先使用第三方API去水印（除非指定优先本地）
    3. 第三方失败时降级到本地逻辑
    4. 返回处理结果和视频信息
    
    **返回说明：**
    - `success`: 处理是否成功
    - `clean_video_url`: 去水印后的视频URL
    - `original_video_info`: 原始视频信息（标题、描述等）
    - `processing_time`: 处理耗时
    - `used_fallback`: 是否使用了降级处理
    """
)
async def remove_watermark(request_data: RemoveWatermarkRequest, req: Request):
    """
    视频去水印接口
    
    Args:
        request_data: 包含密钥和视频URL的请求数据
        req: FastAPI请求对象
    
    Returns:
        去水印处理结果
    """
    try:
        # 验证密钥
        ret, v = verify_key(request_data.key, req)
        if not ret:
            logger.info(f"[remove-watermark]Invalid key {request_data.key}")
            if v == 'out':
                raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="out")
            else:
                raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid key")
        
        # 获取去水印处理器
        watermark_remover = get_watermark_remover()
        
        logger.info(f"[remove-watermark]开始处理视频去水印 - URL: {request_data.url}")
        
        # 执行去水印处理
        result = watermark_remover.remove_watermark(
            video_url=request_data.url,
            prefer_local=request_data.prefer_local
        )
        
        if not result.success:
            logger.error(f"[remove-watermark]去水印处理失败: {result.error_message}")
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, 
                detail=f"去水印处理失败: {result.error_message}"
            )
        
        # 构建成功响应
        response_data = {
            "success": result.success,
            "clean_video_url": result.clean_video_url,
            "original_video_info": result.original_video_info,
            "processing_time": result.processing_time,
            "used_fallback": result.status != "success" or None  # 简化判断，实际可以更精确
        }
        
        logger.info(f"[remove-watermark]去水印处理成功 - 耗时: {result.processing_time:.2f}秒")
        
        return response_data
        
    except HTTPException:
        # HTTPException 不应该被捕获，直接抛出
        raise
    except Exception as e:
        logger.error(f"[remove-watermark]处理过程中发生错误: {e}", exc_info=True)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"处理过程中发生错误: {str(e)}"
        )

# 程序退出时清理资源
import atexit
atexit.register(cleanup_watermark_remover)
        #
        